﻿using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Nvigorate.Data;
using Nvigorate.Relational.Mapping.Index;
using Nvigorate.Relational.UnitOfWork;
using Nvigorate.TestObjects;

namespace Nvigorate.Test.Relational.UnitOfWork
{
    [TestClass]
    public class TransactionalUnitOfWorkTests
    {
        #region Persist Tests

        [TestMethod]
        public void Persist_IfCommitIsNotCalled_DoesNotExecute()
        {
            // Arrange
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            var dataInterface = new Mock<IDataInterface>();
            var person = new Person();
            var command = new Mock<ICommand>();
            var count = 0;
            command.Expect(c => c.Execute()).Callback(() => count++);

            // Act
            var unitOfWork = new UnitOfWorkHarness(repositoryMapIndex.Object, dataInterface.Object)
                                 {
                                     PersistCommandMock = command.Object
                                 };

            unitOfWork.Persist(person, false);

            // Assert
            Assert.AreEqual(0, count);
        }

        #endregion

        #region Delete Tests

        [TestMethod]
        public void Delete_IfCommitIsNotCalled_DoesNotExecute()
        {
            // Arrange
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            var dataInterface = new Mock<IDataInterface>();
            var person = new Person();
            var command = new Mock<ICommand>();
            var count = 0;
            command.Expect(c => c.Execute()).Callback(() => count++);

            // Act
            var unitOfWork = new UnitOfWorkHarness(repositoryMapIndex.Object, dataInterface.Object)
                                 {
                                     DeleteCommandMock = command.Object
                                 };

            unitOfWork.Delete(person, false, true);

            // Assert
            Assert.AreEqual(0, count);
        }

        #endregion

        #region Commit Tests

        [TestMethod]
        public void Commit_IsCalled_WillExecuteCommand()
        {
            // Arrange
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            var dataInterface = new Mock<IDataInterface>();
            var person = new Person();
            var command = new Mock<ICommand>();
            var count = 0;
            command.Expect(c => c.Execute()).Callback(() => count++);

            // Act
            var unitOfWork = new UnitOfWorkHarness(repositoryMapIndex.Object, dataInterface.Object)
                                 {
                                     PersistCommandMock = command.Object
                                 };

            unitOfWork.Persist(person, false);
            unitOfWork.Commit();

            // Assert
            Assert.AreEqual(1, count);
        }

        [TestMethod]
        public void Commit_IsCalledTwice_OnlyExecutesCommandOnce()
        {
            // Arrange
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            var dataInterface = new Mock<IDataInterface>();
            var person = new Person();
            var command = new Mock<ICommand>();
            var count = 0;
            command.Expect(c => c.Execute()).Callback(() => count++);

            // Act
            var unitOfWork = new UnitOfWorkHarness(repositoryMapIndex.Object, dataInterface.Object)
                                 {
                                     PersistCommandMock = command.Object
                                 };

            unitOfWork.Persist(person, false);
            unitOfWork.Commit();
            unitOfWork.Commit();

            // Assert
            Assert.AreEqual(1, count);
        }

        [TestMethod]
        public void Commit_WithMultipleCommands_WillCallCommandsInOrder()
        {
            // Arrange
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            var dataInterface = new Mock<IDataInterface>();
            var person = new Person();
            var persistCommand = new NamedCommand {Name = "First"};
            var deleteCommand = new NamedCommand {Name = "Second"};

            // Act
            var unitOfWork = new UnitOfWorkHarness(repositoryMapIndex.Object, dataInterface.Object)
                                 {
                                     PersistCommandMock = persistCommand,
                                     DeleteCommandMock = deleteCommand
                                 };

            unitOfWork.Persist(person, false);
            unitOfWork.Delete(person, true, true);
            unitOfWork.Commit();

            // Assert
            Assert.AreEqual(2, unitOfWork.ExecutedCommands.Count);
            Assert.AreEqual("First", ((NamedCommand)unitOfWork.ExecutedCommands[0]).Name);
            Assert.AreEqual("Second", ((NamedCommand)unitOfWork.ExecutedCommands[1]).Name);
        }

        #endregion

        #region Nested Types

        private class UnitOfWorkHarness : TransactionalUnitOfWork
        {
            public ICommand PersistCommandMock { get; set; }
            public ICommand DeleteCommandMock { get; set; }
            public readonly List<ICommand> ExecutedCommands = new List<ICommand>();

            public UnitOfWorkHarness(IRepositoryMapIndex repositoryMapIndex, IDataInterface dataInterface)
                : base(repositoryMapIndex, dataInterface)
            {
            }

            protected override ICommand CreatePersistCommand(object instance, bool enforceConcurrency)
            {
                return PersistCommandMock;
            }

            protected override ICommand CreateDeleteCommand(object instance, bool recursive, bool enforceConcurrency)
            {
                return DeleteCommandMock;
            }

            protected override void ExecuteCommand(ICommand command)
            {
                base.ExecuteCommand(command);
                ExecutedCommands.Add(command);
            }
        }

        private class NamedCommand : ICommand 
        {
            public string Name { get; set; }

            public void Execute()
            {
            }
        }

        #endregion
    }
}